<!-- 设计模式：观察者模式 - -->

<!doctype html>
<html>
<head>
  <title>Keypress challenge</title>
  <style>
    #results {
      text-align: center;
      font-size: 36px;
    }
    strong {
      color: blue;
    }
  </style>
</head>
<body>
  <div id="results"></div>
  <script>
    var publisher = {
      subscribers: {
        any: []
      },
      on: function(type, fn, context) {
        type = type || 'any';
        fn = typeof fn === "function" ? fn : context[fn];
        if (typeof this.subscribers[type] === "undefined") {
          this.subscribers[type] = [];
        }
        this.subscribers[type].push({
          fn: fn,
          context: context || this
        });
      },
      remove: function(type, fn, context) {
        this.visitSubscribers('unsubscribe', type, fn, context);
      },
      fire: function(type, publication) {
        this.visitSubscribers('publish', type, publication);
      },
      visitSubscribers: function(action, type, arg, context) {
        var pubtype = type || 'any',
          subscribers = this.subscribers[pubtype],
          i,
          max = subscribers ? subscribers.length : 0;
        for (i = 0; i < max; i += 1) {
          if (action === 'publish') {
            subscribers[i].fn.call(subscribers[i].context, arg);
          } else {
            if (subscribers[i].fn === arg && subscribers[i].context === context) {
              subscribers.splice(i, 1);
            }
          }
        }
      }
    };

    function makePublisher(o) {
      var i;
      for (i in publisher) {
        if (publisher.hasOwnProperty(i) && typeof publisher[i] === "function") {
          o[i] = publisher[i];
        }
      }
      o.subscribers = {
        any: []
      };
    }

    var game = {
      keys: {},
      addPlayer: function(player) {
        var key = player.key.toString().charCodeAt(0);
        this.keys[key] = player;
      },
      handleKeypress: function(e) {
        e = e || window.event; // IE
        if (game.keys[e.which]) {
          game.keys[e.which].play();
        }
      },
      handlePlay: function(player) {
        var i,
          players = this.keys,
          score = {};

        for (i in players) {
          if (players.hasOwnProperty(i)) {
            score[players[i].name] = players[i].points;
          }
        }
        this.fire('scorechange', score);
      }
    };

    function Player(name, key) {
      this.points = 0;
      this.name = name;
      this.key = key;
      this.fire('newplayer', this);
    }
    Player.prototype.play = function() {
      this.points += 1;
      this.fire('play', this);
    };

    var scoreboard = {
      element: document.getElementById('results'),
      update: function(score) {
        var i, msg = '';
        for (i in score) {
          if (score.hasOwnProperty(i)) {
            msg += '<p><strong>' + i + '<\/strong>: ';
            msg += score[i];
            msg += '<\/p>';
          }
        }
        this.element.innerHTML = msg;
      }
    };

    makePublisher(Player.prototype);
    makePublisher(game);

    Player.prototype.on("newplayer", "addPlayer", game);
    Player.prototype.on("play", "handlePlay", game);

    game.on("scorechange", scoreboard.update, scoreboard);
    window.onkeypress = game.handleKeypress;

    var playername, key;
    while (1) {
      playername = prompt("Add player (name)");
      if (!playername) {
        break;
      }
      while (1) {
        key = prompt("Key for " + playername + "?");
        if (key) {
          break;
        }
      }
      new Player(playername, key);
    }
  </script>
</body>
</html>
